package show.me.the.money.config.reportSchema;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.dom4j.Element;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.web.context.WebApplicationContext;

import show.me.the.money.config.reportSchema.define.ReportSchema;
import show.me.the.money.config.schema.SchemaItem;
import show.me.the.money.dao.exception.QueryDataAccessException;
import show.me.the.money.util.ConfigValueParser;
import show.me.the.money.util.context.Context;
import show.me.the.money.util.context.HashMapContext;
import show.me.the.money.util.exp.ExpRunner;


@SuppressWarnings("unchecked")
public class DefaultReportDefine implements ReportDefine{
	private static Log logger = LogFactory.getLog(ReportDefine.class);
	private static String[] aliasList = {"a","b","c","d","e","f","g"};
	protected ReportSchema reportSchema;
	protected List<SchemaItem> headers = new ArrayList<SchemaItem>();
	protected List<SchemaItem> args;
	protected List<Integer> groups = new ArrayList<Integer>();
	protected String[] entryNames;
	protected String[] sorts;
	protected int funcCount;
	protected int fieldCount;
	protected int startRenderIndex;
	List cnds;
	
	public void setDefineXML(Element define){
		List<Element> els = define.selectNodes("headers/item");
		int  i = startRenderIndex;
		for(Element el : els){
			int index = Integer.parseInt(el.attributeValue("renderIndex",String.valueOf(i)));
			SchemaItem si = new SchemaItem(el,index);
			headers.add(si);
			
			if(!si.hasProperty("func")){
				if(!si.isEvalValue()){
					fieldCount ++;
					groups.add(groups.size());
				}
				
			}
			else{
				fieldCount ++;
				funcCount ++;
			}
			i ++;
		}
		
		Element el = define.element("condition");
		if(el != null){
			try {
				ObjectMapper mapper = new ObjectMapper();
				cnds = mapper.readValue(el.getText().replaceAll("'", "\""), List.class);
			} 
			catch (Exception e) {
				logger.error("parse reportCondition failed:", e);
			}
		}		
		el = define.element("src");
		if(el != null){
			els = el.elements("entry");
			entryNames = new String[els.size()];
			i = 0;
			for(Element et : els){
				String nm = et.attributeValue("name",et.getText());
				String alias = et.attributeValue("alias");
				if(alias != null){
					nm += " " + alias;
				}
				entryNames[i] = nm;
				i ++;
			}
			els = el.selectNodes("join/field");
			if(els.size() > 0){
				for(Element f : els){
					String src = f.attributeValue("src");
					String dest = f.attributeValue("dest");
					List joinCnd = new ArrayList();
					joinCnd.add("eq");
					List ref1 = new ArrayList();
					ref1.add("$");
					ref1.add(src);
					joinCnd.add(ref1);
					
					List ref2 = new ArrayList();
					ref2.add("$");
					ref2.add(dest);
					
					
					joinCnd.add(ref2);
					if(cnds == null){
						cnds = joinCnd;
					}
					else{
						if(cnds.get(0).equals("and")){
							cnds.add(joinCnd);
						}
						else{
							List o = new ArrayList();
							o.add("and");
							o.add(joinCnd);
							o.add(cnds);
							cnds = o;							
						}
					}
				}
			}
			el = define.element("sort");
			if(el != null){
				els = el.elements();
				if(els.size() == 0){
					sorts = new String[]{el.attributeValue("id",el.getText()) + " " + el.attributeValue("dict","")};
				}
				else{
					sorts = new String[els.size()];
					i = 0;
					for(Element f:els){
						sorts[i] = f.attributeValue("id",el.getText()) + " " + f.attributeValue("dict","");
						i ++;
					}
				}
			}
		}
		else{
			logger.error("src not defined...");
		}
		
	}
	
	public List<SchemaItem> getHeaders(boolean group){
		List<SchemaItem> ls = new ArrayList<SchemaItem>();
		for(SchemaItem i : headers){
			if(!i.hasProperty("func") && !group && !i.isEvalValue()){
				continue;
			}
			ls.add(i);
		}
		return ls;
	}
	
	public void setReportSchema(ReportSchema rs){
		reportSchema = rs;
	}
	
	public void setQueryArgs(List<SchemaItem> queryArgs){
		args = queryArgs;
	}
	
	public void addCondition(List topCnd){
		if(cnds == null){
			cnds = topCnd;
		}
		else{
			if(cnds.get(0).equals("and")){
				cnds.add(topCnd);
			}
			else{
				List o = new ArrayList();
				o.add("and");
				o.add(cnds);
				o.add(topCnd);
				cnds = o;
			}
		}
	}
	
	public List<HashMap> runSingleMode(Context ctx){
		WebApplicationContext wac = (WebApplicationContext)ctx.get(Context.APP_CONTEXT);
		SessionFactory sf = (SessionFactory)wac.getBean("mySessionFactory");
		Session ss = null;
		List<HashMap> data = new ArrayList<HashMap>();
		try{
			ss = sf.openSession();
			String hql = prepareHql(ctx);
			if(StringUtils.isEmpty(hql)){
				throw new QueryDataAccessException("init hql failed");
			}
			else{
				Query q = ss.createQuery(hql);
				List<Object[]> rs = q.list();
				int totalCount = rs.size();
				ctx.put("totalCount", totalCount);
				ctx.put("pageSize", totalCount);
				ctx.put("pageNo", 1);
				if(totalCount > 0){
					int fieldCount = headers.size();
					if(fieldCount > 1){
						for(Object[] r : rs){
							HashMap o = new HashMap();
							data.add(o);
							Context rCtx = new HashMapContext(o);
							ctx.putCtx("r", rCtx);
							int i = 0;
							for(SchemaItem f : headers){
								String k = f.getId();
								Object v = null;
								if(f.isEvalValue()){
									v = ExpRunner.run(f.exp(), ctx);
								}
								else{
									v = r[i];
									i ++;
								}
								o.put(k,v);
								
								if(f.isCodedValue()){
									o.put(k + "_text", f.getDisplayValue(v));
								}
								
							}
						}
					}
					else{ //fieldCount
						Object v = rs.get(0);
						if(v == null){
							return data;
						}
						HashMap o = new HashMap();
						data.add(o);
						o.put(headers.get(0).getId(), v);
						
					}
				}
			}
		}
		catch(Exception e){
			e.printStackTrace();
			logger.error("run SingleMode failed", e);
		}
		finally{
			if(ss != null && ss.isOpen()){
				ss.close();
			}
		}
		return data;
	}
	
	public void runMutiMode(HashMap<String,HashMap> data,Context ctx){
		WebApplicationContext wac = (WebApplicationContext)ctx.get(Context.APP_CONTEXT);
		SessionFactory sf = (SessionFactory)wac.getBean("mySessionFactory");
		Session ss = null;
		try{
			ss = sf.openSession();
			String hql = prepareHql(ctx);
			if(StringUtils.isEmpty(hql)){
				throw new QueryDataAccessException("init hql failed");
			}
			else{
				Query q = ss.createQuery(hql);
				List<Object[]> rs = q.list();
				int totalCount = rs.size();
				if(totalCount > 0){
					if(fieldCount > 1){
						for(Object[] r : rs){
							StringBuffer groupStr = new StringBuffer();
							if(groups.size() > 0){
								if(funcCount > 0){
									for(Integer gi:groups){
										groupStr.append(r[gi]);
									}
								}
								else{
									groupStr.append(r[0]);
								}
							}
							else{
								groupStr.append("0");
							}
							String key = groupStr.toString();
							HashMap o = data.get(key);
							
							if(o == null){
								o = reportSchema.createEmptyRecord();
								data.put(key, o);
							}
							Context rCtx = new HashMapContext(o);
							ctx.putCtx("r", rCtx);
							int i =0;
							for(SchemaItem f : headers){
								
								String k = f.getId();
								Object v = null;
								
								if(f.isEvalValue()){
									
									v = ExpRunner.run(f.exp(), ctx);
								}
								else{
									v = r[i];
									i ++;
								}
								if(v == null){
									continue;
								}
								o.put(k,v);
								if(f.isCodedValue()){
									o.put(k + "_text", f.getDisplayValue(v));
								}
							}
						}//for rs
					}//for fieldCount
					else{
						Object v = rs.get(0);
						String key = "0";
						HashMap o = data.get(key);
						if(o == null){
							o = reportSchema.createEmptyRecord();
							data.put(key, o);
						}
						if(v == null){
							return;
						}
						o.put(headers.get(0).getId(), v);
						Context rCtx = new HashMapContext(o);
						ctx.putCtx("r", rCtx);
						for(SchemaItem f : headers){
							String k = f.getId();
							if(f.isEvalValue()){
								v = ExpRunner.run(f.exp(), ctx);
								o.put(k,v);
							}
							
							if(f.isCodedValue()){
								o.put(k + "_text", f.getDisplayValue(v));
							}
						}
					}
				}//totalCount
			}
		}
		catch(Exception e){
			e.printStackTrace();
			logger.error("run mutiMode failed", e);
		}
		finally{
			if(ss != null && ss.isOpen()){
				ss.close();
			}
		}		
	}
	
	protected String prepareHql(Context ctx){
		StringBuffer hql = new StringBuffer();
		if(headers == null || headers.size() == 0){
			logger.error("ReportDefine headers not found");
			return "";
		}
		StringBuffer hqlFields = new StringBuffer();
		StringBuffer hqlGroup = new StringBuffer();
		for(SchemaItem f : headers){
			if(f.isEvalValue()){
				continue;
			}
			String expr = f.hasProperty("expr") ? f.getProperty("expr") : f.getId();
			if(f.hasProperty("func")){
				expr = f.getProperty("func") + "(" + expr + ")";
			}
			else{
				if(funcCount > 0){
					hqlGroup.append(expr).append(",");
				}
			}
			hqlFields.append(expr).append(",");
		}
		
		String select = hqlFields.substring(0,hqlFields.length() - 1);
		String from = "";
		if(entryNames.length == 1){
			from = entryNames[0];
		}
		else{
			int  i = 0;
			for(String nm : entryNames){
				from += nm + " " + aliasList[i] + ",";
				i ++;
			}
			from = from.substring(0,from.length() - 1);
		}
		String where = "";
		String group = "";
		int gCount = groups.size();
		if(gCount > 0 && funcCount > 0){	
			group = hqlGroup.substring(0,hqlGroup.length() - 1);
		}
		String orderBy = "";
		if(cnds != null){
			if(args != null){
				int argCount = args.size();
				for(int j = 0; j < argCount; j ++){
					SchemaItem a = args.get(j);
					String argName = "q." + a.getId();
					String dv = a.getDefaultValue();
					if(!ctx.has(argName) && dv != null){
						String v = ConfigValueParser.parse(dv,ctx);
						ctx.put(argName, v);
					}
				}
			}

			where =  " where " + ExpRunner.toString(cnds,ctx);
		}
		if(sorts != null){
			 orderBy = " order by ";
			 for(String st:sorts){
				 orderBy += st + ",";
			 }
			 orderBy = orderBy.substring(0,orderBy.length() - 1);
		}
		hql.append("select ").append(select.toString())
			.append(" from ").append(from)
				.append(where);
		if(group.length() > 0){
			hql.append(" group by ").append(group);
		}
		hql.append(orderBy);
		return hql.toString();
	}

	public int getHeaderCount() {
		return headers.size();
	}

	public void setStartRenderIndex(int start) {
		startRenderIndex = start;
	}
	
	
}
